// This File is part of the "nyaqt" Project
// 
// Copyright  2009 sometmes@gmail.com
// All rights reserved
// 
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.



using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using nyaqt.Plugins;
using System.Data;
namespace nyaqt
{
    class DatabaseTreeController
    {
        TreeView _treeView;
        BackgroundWorker2 _loader = new BackgroundWorker2();
        LoaderTreeNode _loaderNode = null;
        Stack<string> _history = new Stack<string>();

        Dictionary<string, ContextMenuStrip> _treeMenus;
        public Dictionary<string, ContextMenuStrip> TreeMenus
        {
            get { return _treeMenus; }
            set { _treeMenus = value; }
        }

        private TreeNode _contextNode;
        public TreeNode ContextNode
        {
            get { return _contextNode; }
            set { _contextNode = value; }
        }

        public DatabaseTreeController(TreeView view)
        {
            _treeView = view;
            _treeView.AfterExpand += new TreeViewEventHandler(_treeView_AfterExpand);
            _treeView.MouseDoubleClick += new MouseEventHandler(_treeView_MouseDoubleClick);
            _treeView.KeyDown += new KeyEventHandler(_treeView_KeyDown);
            _loader.DoWork += new System.ComponentModel.DoWorkEventHandler(_loader_DoWork);
            _loader.WorkerReportsProgress = false;
            _loader.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(_loader_RunWorkerCompleted);
        }

        System.ComponentModel.RunWorkerCompletedEventHandler _transientWorkerCompleteHandler;
        public void LoadNodeAssinc(TreeNode node, System.ComponentModel.RunWorkerCompletedEventHandler workerCompletedHandler)
        {
            if (node is LoaderTreeNode)
            {
                LoaderTreeNode loaderNode = node as LoaderTreeNode;
                if (!loaderNode.IsLoaded)
                {
                    if (_loader.IsBusy) _loader.Abort();
                    _loaderNode = loaderNode;
                    _transientWorkerCompleteHandler = workerCompletedHandler;
                    _loader.RunWorkerAsync();
                }
            }
            else
            {
                throw new SystemException("Node to load must be a LoaderTreeNode");
            }

        }

        void AbortLoader(LoaderTreeNode loaderNode, BackgroundWorker2 bgw)
        {
            bgw.Abort();
            loaderNode.Collapse();
            ConnectionInfo info = _loaderNode.ServerNode.Connection;
            info.Current = info.CreateConnection();
        }

        void _treeView_AfterExpand(object sender, TreeViewEventArgs e)
        {
            if (e.Node is LoaderTreeNode)
            {
                LoaderTreeNode loaderNode = e.Node as LoaderTreeNode;
                if (!loaderNode.IsLoaded)
                {
                    if (_loader.IsBusy)
                        AbortLoader(_loaderNode, _loader);
                    _loaderNode = loaderNode;
                    _loader.RunWorkerAsync();
                }
            }
        }

        void _loader_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            if (_loaderNode.NodeLoaderType == NodeLoaderType.Databases)
                AddDatabaseNodes((List<DatabaseEntityInfo>)e.Result);
            else
                AddEntityNodes((List<DatabaseEntityInfo>)e.Result);

            _loaderNode.IsLoaded = true;

            if (_transientWorkerCompleteHandler != null)
            {
                System.ComponentModel.RunWorkerCompletedEventHandler ev = _transientWorkerCompleteHandler;
                _transientWorkerCompleteHandler = null;
                ev.Invoke(_loaderNode, e);
            }
        }

        void _loader_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            IConnectionProperties cn = _loaderNode.ServerNode.Connection.ConnectionProperties;
            if (cn.Connection.State != ConnectionState.Open)
                cn.Connection.Open();

            if (_loaderNode.NodeLoaderType == NodeLoaderType.Databases)
                e.Result = cn.GetDatabases();
            else if (_loaderNode.NodeLoaderType == NodeLoaderType.Tables)
                e.Result = cn.GetDatabaseTables(_loaderNode.Entity.DatabaseName);
            else if (_loaderNode.NodeLoaderType == NodeLoaderType.SystemTables)
                e.Result = cn.GetDatabaseSystemTables(_loaderNode.Entity.DatabaseName);
            else if (_loaderNode.NodeLoaderType == NodeLoaderType.Views)
                e.Result = cn.GetDatabaseViews(_loaderNode.Entity.DatabaseName);
            else if (_loaderNode.NodeLoaderType == NodeLoaderType.StoredProc)
                e.Result = cn.GetDatabaseStoredProcedures(_loaderNode.Entity.DatabaseName);
            else if (_loaderNode.NodeLoaderType == NodeLoaderType.Columns)
                e.Result = cn.GetDatabaseTableColumns(_loaderNode.Entity.DatabaseName, _loaderNode.Entity.TableNamespace, _loaderNode.Entity.TableName);
            else if (_loaderNode.NodeLoaderType == NodeLoaderType.Relations)
                e.Result = cn.GetDatabaseTableRelations(_loaderNode.Entity.DatabaseName, _loaderNode.Entity.TableNamespace, _loaderNode.Entity.TableName);
            else if (_loaderNode.NodeLoaderType == NodeLoaderType.RelationColumns)
            {
                if (_loaderNode.Entity.IsOutGoing)
                    e.Result = cn.GetDatabaseTableRelationColumns(_loaderNode.Entity.DatabaseName, _loaderNode.Entity.TableNamespace, _loaderNode.Entity.TableName, _loaderNode.Entity.RelatedTableNamespace, _loaderNode.Entity.RelatedTable);
                else
                    e.Result = cn.GetDatabaseTableRelationColumns(_loaderNode.Entity.DatabaseName, _loaderNode.Entity.RelatedTableNamespace, _loaderNode.Entity.RelatedTable, _loaderNode.Entity.TableNamespace, _loaderNode.Entity.TableName);
            }
            else if (_loaderNode.NodeLoaderType == NodeLoaderType.StoredProcParameters)
                e.Result = cn.GetDatabaseStoredProcedureParameters(_loaderNode.Entity.DatabaseName, _loaderNode.Entity.TableNamespace, _loaderNode.Entity.TableName);
        }

        LoaderTreeNode AddLoaderNode(TreeNode parent, NodeLoaderType type, string text, DatabaseEntityInfo entity, ServerTreeNode serverNode)
        {
            LoaderTreeNode node = new LoaderTreeNode();
            node.Text = text;
            node.Entity = entity;
            node.ImageIndex = 1;
            node.SelectedImageIndex = 1;
            node.StateImageIndex = 1;
            node.NodeLoaderType = type;
            node.ServerNode = serverNode;
            WaitTreeNode loading = new WaitTreeNode();
            node.Nodes.Add(loading);
            parent.Nodes.Add(node);
            return node;
        }

        private void AddEntityNodes(List<nyaqt.Plugins.DatabaseEntityInfo> list)
        {
            ClearLeafNodes(_loaderNode);
            foreach (DatabaseEntityInfo entityInfo in list)
            {
                EntityTreeNode entityNode = new EntityTreeNode(entityInfo.DisplayName);
                if (!string.IsNullOrEmpty(entityInfo.ClipboardText1))
                {
                    string[] clip = new string[2];
                    clip[0] = entityInfo.ClipboardText1;
                    clip[1] = entityInfo.ClipboardText2;
                    entityNode.Tag = clip;
                }
                entityNode.Entity = entityInfo;
                entityNode.ServerNode = _loaderNode.ServerNode;
                if (entityInfo.TableType == "TABLE")
                {
                    if (_treeMenus.ContainsKey(entityInfo.TableType))
                        entityNode.ContextMenuStrip = _treeMenus[entityInfo.TableType];
                    entityNode.ImageIndex = 2;
                    entityNode.SelectedImageIndex = 2;
                    entityNode.StateImageIndex = 2;
                    LoaderTreeNode cols = AddLoaderNode(entityNode, NodeLoaderType.Columns, "Columns", entityInfo, _loaderNode.ServerNode);
                    LoaderTreeNode rels = AddLoaderNode(entityNode, NodeLoaderType.Relations, "Relations", entityInfo, _loaderNode.ServerNode);
                }
                if (entityInfo.TableType == "SYSTEM TABLE")
                {
                    if (_treeMenus.ContainsKey(entityInfo.TableType))
                        entityNode.ContextMenuStrip = _treeMenus[entityInfo.TableType];
                    entityNode.ImageIndex = 2;
                    entityNode.SelectedImageIndex = 2;
                    entityNode.StateImageIndex = 2;
                }
                if (entityInfo.TableType == "VIEW")
                {
                    if (_treeMenus.ContainsKey(entityInfo.TableType))
                        entityNode.ContextMenuStrip = _treeMenus[entityInfo.TableType];
                    entityNode.ImageIndex = 3;
                    entityNode.SelectedImageIndex = 3;
                    entityNode.StateImageIndex = 3;
                }
                if (entityInfo.TableType == "STORED PROC")
                {
                    if (_treeMenus.ContainsKey(entityInfo.TableType))
                        entityNode.ContextMenuStrip = _treeMenus[entityInfo.TableType];
                    entityNode.ImageIndex = 4;
                    entityNode.SelectedImageIndex = 4;
                    entityNode.StateImageIndex = 4;
                    LoaderTreeNode cols = AddLoaderNode(entityNode, NodeLoaderType.StoredProcParameters, "Parameters", entityInfo, _loaderNode.ServerNode);
                }
                if (entityInfo.TableType == "COLUMN")
                {
                    if (_treeMenus.ContainsKey(entityInfo.TableType))
                        entityNode.ContextMenuStrip = _treeMenus[entityInfo.TableType];
                    if (entityInfo.IsPrimaryKey)
                    {
                        entityNode.ImageIndex = 7;
                        entityNode.SelectedImageIndex = 7;
                        entityNode.StateImageIndex = 7;
                    }
                    else
                    {
                        entityNode.ImageIndex = 8;
                        entityNode.SelectedImageIndex = 8;
                        entityNode.StateImageIndex = 8;
                    }
                }
                if (entityInfo.TableType == "RELATION")
                {
                    if (_treeMenus.ContainsKey(entityInfo.TableType))
                        entityNode.ContextMenuStrip = _treeMenus[entityInfo.TableType];
                    entityNode.ImageIndex = 2;
                    entityNode.SelectedImageIndex = 2;
                    entityNode.StateImageIndex = 2;
                    LoaderTreeNode rels = AddLoaderNode(entityNode, NodeLoaderType.RelationColumns, "Relation Columns", entityInfo, _loaderNode.ServerNode);
                }
                if (entityInfo.TableType == "RELATION COLUMN")
                {
                    if (_treeMenus.ContainsKey(entityInfo.TableType))
                        entityNode.ContextMenuStrip = _treeMenus[entityInfo.TableType];
                    entityNode.ImageIndex = 9;
                    entityNode.SelectedImageIndex = 9;
                    entityNode.StateImageIndex = 9;
                }
                else if (entityInfo.TableType == "ERROR")
                {
                    if (_treeMenus.ContainsKey(entityInfo.TableType))
                        entityNode.ContextMenuStrip = _treeMenus[entityInfo.TableType];
                    entityNode.ImageIndex = 6;
                    entityNode.SelectedImageIndex = 6;
                    entityNode.StateImageIndex = 6;
                }
              
                _loaderNode.Nodes.Add(entityNode);
            }
                    
        }

        void ClearLeafNodes(TreeNode parent)
        {
            List<TreeNode> toRemove = new List<TreeNode>();
            foreach (TreeNode node in parent.Nodes)
                if (node.SelectedImageIndex != 1)
                    toRemove.Add(node);

            foreach (TreeNode node in toRemove)
                node.Remove();
        }

        private void AddDatabaseNodes(List<nyaqt.Plugins.DatabaseEntityInfo> list)
        {
            ClearLeafNodes(_loaderNode);
            TreeNode focused = null;
            foreach (DatabaseEntityInfo dbInfo in list)
            {
                TreeNode dbNode = new TreeNode(dbInfo.DatabaseName);
                if (dbInfo.DatabaseName == _loaderNode.ServerNode.InitialDB)
                    focused = dbNode;
                dbNode.ImageIndex = 0;
                dbNode.SelectedImageIndex = 0;
                dbNode.StateImageIndex = 0;
                _loaderNode.Nodes.Add(dbNode);
                // Tables
                LoaderTreeNode tables = AddLoaderNode(dbNode, NodeLoaderType.Tables, "Tables", dbInfo, _loaderNode.ServerNode);
                // System Tables
                LoaderTreeNode systables = AddLoaderNode(tables, NodeLoaderType.SystemTables, "System Tables", dbInfo, _loaderNode.ServerNode);
                // Views
                LoaderTreeNode views = AddLoaderNode(dbNode, NodeLoaderType.Views, "Views", dbInfo, _loaderNode.ServerNode);
                // Storeds
                LoaderTreeNode storeds = AddLoaderNode(dbNode, NodeLoaderType.StoredProc, "Stored Procedures", dbInfo, _loaderNode.ServerNode);
            }
            if (focused != null)
                _treeView.SelectedNode = focused;
        }

        bool FocusServer(ConnectionInfo ci)
        {
            string newServerBrowseKey = ci.ProviderInvariantName + ";" + ci.Version + "/" + ci.ConnectionProperties.ServerBrowseKey;

            foreach (ServerTreeNode server in _treeView.Nodes)
            {
                if (server.ServerBrowseKey == newServerBrowseKey)
                {
                    server.InitialDB = ci.ConnectionProperties.Database;
                    FocusServer(server);
                    return true;
                }
            }
            return false;
        }

        public void AddServer(ConnectionInfo ci, ConnectionInfo referenceCn)
        {
            if (ci != null && ci.Current != null)
            {
                if (FocusServer(ci))
                        return;
            }
            else if (referenceCn != null && referenceCn.Current != null)
            {
                if (FocusServer(referenceCn))
                        return;
            }

            if (ci == null || ci.Current == null)
            {
                NewConnectionForm f = new NewConnectionForm();
                f.Connection = ci;
                if (DialogResult.OK == f.ShowDialog())
                {
                    AddServerNode(f.Connection);
                }
            }
            else
            {
                AddServerNode(ci);
            }
        }

        private void AddServerNode(ConnectionInfo ci)
        {
            string newServerBrowseKey = ci.ProviderInvariantName + ";" + ci.Version + "/" + ci.ConnectionProperties.ServerBrowseKey;

            ServerTreeNode server = new ServerTreeNode();
            server.InitialDB = ci.ConnectionProperties.Database;
            server.Connection = ci;
            server.Text = ci.ConnectionProperties.Server;
            server.ServerBrowseKey = newServerBrowseKey;
            server.ImageIndex = 0;
            server.SelectedImageIndex = 0;
            server.StateImageIndex = 0;
            LoaderTreeNode dbs = new LoaderTreeNode();
            dbs.Text = "Databases";
            dbs.ImageIndex = 1;
            dbs.SelectedImageIndex = 1;
            dbs.StateImageIndex = 1;
            dbs.NodeLoaderType = NodeLoaderType.Databases;
            dbs.ServerNode = server;
            server.Nodes.Add(dbs);
            WaitTreeNode loading = new WaitTreeNode();
            dbs.Nodes.Add(loading);
            _treeView.Nodes.Add(server);
            _treeView.SelectedNode = server;
        }

        private void FocusServer(ServerTreeNode server)
        {
            if (server.Tables.IsLoaded)
            {
                foreach (TreeNode db in server.Tables.Nodes)
                {
                    if (db.Text == server.InitialDB)
                    {
                        _treeView.SelectedNode = db;
                        return;
                    }
                }
            }
            _treeView.SelectedNode = server;
        }

        public void NavigateToTable(TreeNode relationNode)
        {
            EntityTreeNode relation = relationNode as EntityTreeNode;
            TreeNodeCollection tables = relation.Parent.Parent.Parent.Nodes; // relations folder.table.tables folder.Nodes
            EntityTreeNode found = null;
            foreach (TreeNode node in tables)
            {
                EntityTreeNode table = node as EntityTreeNode;
                if (table != null && table.Entity.TableName == relation.Entity.RelatedTable && table.Entity.TableNamespace == relation.Entity.RelatedTableNamespace)
                {
                    found = table;
                    break;
                }
            }
            if (found != null)
            {
                _history.Push(relationNode.FullPath);
                _treeView.SelectedNode = found;
            }
        }

        void _treeView_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            TreeNode node = _treeView.GetNodeAt(_treeView.PointToClient(Control.MousePosition));
            EntityTreeNode entNode = node as EntityTreeNode;
            if (entNode != null && entNode.Entity.TableType == "RELATION")
            {
                NavigateToTable(node);
            }
        }

        void _treeView_KeyDown(object sender, KeyEventArgs e)
        {
            TreeNode node = _treeView.SelectedNode;
            EntityTreeNode entNode = node as EntityTreeNode;
            if (entNode != null && entNode.Entity.TableType == "RELATION")
            {
                if (e.KeyCode == Keys.Space)
                {
                    e.Handled = true;
                    NavigateToTable(node);
                    return;
                }
            }
            if (e.KeyCode == Keys.Back)
            {
                if (_history.Count > 0)
                {
                    TreeNode newnode = GetNodeFromPath(_history.Pop());
                    _treeView.SelectedNode = newnode;
                    e.Handled = true;
                }
            }
            if (e.KeyCode == Keys.C && e.Control)
            {
                if (node == null) return;
                if (node.Tag as string[] != null)
                {
                    if (e.Shift)
                        Clipboard.SetText((node.Tag as string[])[1]);
                    else
                        Clipboard.SetText((node.Tag as string[])[0]);
                }
                else
                    Clipboard.SetText(node.Text);
            }
        }

        string GetNodeKeysPath(TreeNode node)
        {
            List<string> keys = new List<string>();
            while (node != null)
            {
                keys.Add(node.Name);
                node = node.Parent;
            }
            keys.Reverse();
            return string.Join(_treeView.PathSeparator, keys.ToArray());
        }

        TreeNode GetNodeFromPath(string fullPath)
        {
            string[] steps = fullPath.Split(new string[] { _treeView.PathSeparator },StringSplitOptions.None);
            TreeNode node = null;
            foreach (string step in steps)
            {
                TreeNodeCollection nodes;
                if (node == null)
                    nodes = _treeView.Nodes;
                else
                    nodes = node.Nodes;
                foreach (TreeNode n in nodes)
                {
                    if (n.Text == step)
                    {
                        node = n;
                        break;
                    }
                }
                if (node == null)
                    break;
            }
            return node;
        }

        internal class ServerTreeNode : TreeNode
        {
            internal LoaderTreeNode Tables
            {
                get
                {
                    return this.Nodes[0] as LoaderTreeNode;
                }
            }

            string _initialDB;
            public string InitialDB
            {
                get { return _initialDB; }
                set { _initialDB = value; }
            }

            string _serverBrowseKey;
            public string ServerBrowseKey
            {
                get { return _serverBrowseKey; }
                set { _serverBrowseKey = value; }
            }

            ConnectionInfo _connection;
            public ConnectionInfo Connection
            {
                get { return _connection; }
                set { _connection = value; }
            }
        }

        internal enum NodeLoaderType
        {
            Databases,
            Tables,
            SystemTables,
            Views,
            StoredProc,
            Columns,
            Relations,
            RelationColumns,
            StoredProcParameters
        }

        internal class WaitTreeNode : TreeNode
        {
            public WaitTreeNode()
            {
                this.Text = "Loading...";
                this.ImageIndex = 5;
                this.SelectedImageIndex = 5;
                this.StateImageIndex = 5;
            }
        }

        internal class EntityTreeNode : TreeNode
        {
            public EntityTreeNode()
            {
            }

            public EntityTreeNode(string text)
                : base(text)
            {
            }

            ServerTreeNode _serverNode;
            public ServerTreeNode ServerNode
            {
                get { return _serverNode; }
                set { _serverNode = value; }
            }

            DatabaseEntityInfo _entity;
            public DatabaseEntityInfo Entity
            {
                get { return _entity; }
                set { _entity = value; }
            }

            public IConnectionProperties ConnectionProperties
            {
                get
                {
                    return this.ServerNode.Connection.ConnectionProperties;
                }
            }
        }

        internal class LoaderTreeNode : TreeNode
        {
            bool _isLoaded = false;
            public bool IsLoaded
            {
                get { return _isLoaded; }
                set { _isLoaded = value; }
            }

            NodeLoaderType _nodeLoaderType;
            public NodeLoaderType NodeLoaderType
            {
                get { return _nodeLoaderType; }
                set { _nodeLoaderType = value; }
            }

            ServerTreeNode _serverNode;
            public ServerTreeNode ServerNode
            {
                get { return _serverNode; }
                set { _serverNode = value; }
            }

            DatabaseEntityInfo _entity;
            public DatabaseEntityInfo Entity
            {
                get { return _entity; }
                set { _entity = value; }
            }

            DatabaseEntityInfo _relatedEntity;
            public DatabaseEntityInfo RelatedEntity
            {
                get { return _relatedEntity; }
                set { _relatedEntity = value; }
            }


            //string _databaseName;
            //public string DatabaseName
            //{
            //    get { return _databaseName; }
            //    set { _databaseName = value; }
            //}

            //string _tableNamespace;
            //public string TableNamespace
            //{
            //    get { return _tableNamespace; }
            //    set { _tableNamespace = value; }
            //}

            //string _tableName;
            //public string TableName
            //{
            //    get { return _tableName; }
            //    set { _tableName = value; }
            //}

            //string _relatedTable;
            //public string RelatedTable
            //{
            //    get { return _relatedTable; }
            //    set { _relatedTable = value; }
            //}

            //string _relatedTableNamespace;
            //public string RelatedTableNamespace
            //{
            //    get { return _relatedTableNamespace; }
            //    set { _relatedTableNamespace = value; }
            //}
        }

        public void RefreshNode()
        {
            TreeNode node = _treeView.SelectedNode;
            if (!(node is LoaderTreeNode))
                return;

            node.Collapse();
            ClearLoadedNodes(node);
            WaitTreeNode loading = new WaitTreeNode();
            node.Nodes.Add(loading);
            (node as LoaderTreeNode).IsLoaded = false;
            node.Expand();
        }

        private void ClearLoadedNodes(TreeNode parent)
        {
            List<TreeNode> todelete = new List<TreeNode>();
            foreach (TreeNode node in parent.Nodes)
            {
                if (node is EntityTreeNode)
                    todelete.Add(node);
            }
            foreach (TreeNode node in todelete)
                node.Remove();
        }
    }
}
































